第二十二章笔记

1 作用域安全函数,以及如何防止this跑掉。使用寄生模式。

 /* 作用域安全的构造函数,我们可能会忘记在类名之前加new,所以我们可以构造一个
   作用域安全的函数,比如下面的person,每次都要确定this指没指向Person。
   */
function Person(name, age, job) {
    if (this instanceof Person) {
        this.name = name;
        this.age = age;
        this.job = job;
    } else {
        return new Person(name, age, job);
    }
}
var person1 = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //""
alert(person1.name); //"Nicholas"
var person2 = new Person("Shelby", 34, "Ergonomist");
alert(person2.name); //"Shelby" 


/*你会锁定这个作用域环境,但是一旦你在另一个对象中初始话Person就会出现问题比如*/
function Polygon(sides) {
    if (this instanceof Polygon) {
        this.sides = sides;
        this.getArea = function () {

            return 0;
        };
    } else {
        return new Polygon(sides);
    }
}
function Rectangle(width, height) {
    Polygon.call(this, 2);
    this.width = width;
    this.height = height;
    this.getArea = function () {
        return this.width * this.height;
    };
}
var rect = new Rectangle(5, 10);
alert(rect.sides); //undefined 

在这段代码中,Polygon 构造函数是作用域安全的,然而 Rectangle 构造函数则不是。新创建一
个 Rectangle 实例之后,这个实例应该通过 Polygon.call()来继承 Polygon 的 sides 属性。但是,
由于 Polygon 构造函数是作用域安全的,this 对象并非 Polygon 的实例,所以会创建并返回一个新
的 Polygon 对象。Rectangle 构造函数中的 this 对象并没有得到增长,同时 Polygon.call()返回
的值也没有用到,所以 Rectangle 实例中就不会有 sides 属性。

/改进之后我们可以/

function Polygon(sides) {
    if (this instanceof Polygon) {
        this.sides = sides;
        this.getArea = function () {
            return 0;
        };
    } else {
        return new Polygon(sides);
    }
}
function Rectangle(width, height) {
    Polygon.call(this, 2);
    this.width = width;
    this.height = height;
    this.getArea = function () {
        return this.width * this.height;
    };
}
Rectangle.prototype = new Polygon();
var rect = new Rectangle(5, 10);
alert(rect.sides); //2 组,那么以上代码就会返回 false。

2函数绑定

另一个日益流行的高级技巧叫做函数绑定。函数绑定要创建一个函数,可以在特定的 this 环境中
以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量
传递的同时保留代码执行环境。

/我们实现了函数绑定好吧。将函数绑定到特定的环境中,这样,函数就可以访问到对应的变量了/

Html

<button id="mybtn">my</button>

Javascript

function bind(fn, context) {
        return function () {
            return fn.apply(context, arguments);
        };
    } 

        var handler = {
            message: "Event handled",
            handleClick: function (event) {
                alert(this.message);
            }
        };
        var btn = document.getElementById("mybtn");

        //这里的handler.handleClick函数可以访问到handler对象环境中的message
        EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));

3高级定时器

我们需要明白原本的工作线程机制以及加入定时器的工作机制。

  • 定时器对队列的工作方式是,当特定时间过去后将代码插入。注意,给队列添加代码并不意味着对
    它立刻执行,而只能表示它会尽快执行。设定一个 150ms 后执行的定时器不代表到了 150ms 代码就立刻
    执行,它表示代码会在 150ms 后被加入到队列中。如果在这个时间点上,队列中没有其他东西,那么这
    段代码就会被执行,表面上看上去好像代码就在精确指定的时间点上执行了。其他情况下,代码可能明
    显地等待更长时间才执行。
  • 一旦某个函数需要花 50ms 以上的时间完成,那么最好看看能否将任务分割为一
    系列可以使用定时器的小任务。
setTimeout(function(){
 //取出下一个条目并处理
 var item = array.shift();
 process(item);
 //若还有条目,再设置另一个定时器
 if(array.length > 0){
 setTimeout(arguments.callee, 100);
 }
}, 100); 

4函数节流

只要代码是周期性执行的,都应该使用节流,但是你不能控制请求执行的速率。这里展示的
throttle()函数用了 100ms 作为间隔,你当然可以根据你的需要来修改它。

     function throttle(method, context) {
    clearTimeout(method.tId);
    method.tId = setTimeout(function () {
        method.call(context);
    }, 100);
}

function resizeDiv() {
    var div = document.getElementById("myDiv");
    div.style.height = div.offsetWidth + "px";
}

/*下面的界面装换就会频繁的更换大小,特别当屏幕变化时,它需要节流。你调用它20次,但其实它只调用了两次*/
window.onresize = function () {
    throttle(resizeDiv);
}; 

5自定义事件,好好学习下

/自定义事件背后的概念是创建一个管理事件的对象,让其他对象监听那些事件。/

 function EventTarget(){

     this.handlers={};//事件的处理程序序列
 }

EventTarget.prototype = {
    constructor: EventTarget,
    addHandler: function (type, handler) {
        if (typeof this.handlers[type] == "undefined") {

            this.handlers[type] = [];
        }
        this.handlers[type].push(handler);
    },
         //激发事件
    fire: function (event) {
        if (!event.target) {
            event.target = this;
        }
        if (this.handlers[event.type] instanceof Array) {
            var handlers = this.handlers[event.type];
            for (var i = 0, len = handlers.length; i < len; i++) {
                handlers[i](event);
            }
        }
    },
    /*移除事件*/
    removeHandler: function (type, handler) {
        if (this.handlers[type] instanceof Array) {
            var handlers = this.handlers[type];
            for (var i = 0, len = handlers.length; i < len; i++) {
                if (handlers[i] === handler) {
                    break;
                }
            }
            handlers.splice(i, 1);

        }
    }
}; 

/如何使用自定义事件呢?/

function handleMessage(event){

    alert("Message received:"+event.message);
}
//创建一个新对象
var target=new EventTarget();

//添加一个事件处理程序
target.addHandler("message",handleMessage);
//触发事件

target.fire({type:"message",message:"hello world"});
//删除事件处理程序

target.removeHandler("message",handleMessage);
//再次,应没有处理程序
target.fire({type:"message",message:"hello world"});

/那么这种事件可以通过继承的方式给别人,让别的控件或者div也能拥有这个事件类型的处理程序等等/

5拖拽实现

<html>

<title>login</title>

<body>


<div   id="myDiv" style="width: 200px;height: 200px; border:1px solid #F00">


</div>

<div class="draggable" style="position:absolute; background:red;width: 300px;height: 300px;"  > </div> 

<script>



var EventUtil = {

    addHandler: function (element, type, handler) {
        if (element.addEventListener) {
            element.addEventListener(type, handler, false);
        } else if (element.attachEvent) {
            element.attachEvent("on" + type, handler);
        } else {
            element["on" + type] = handler;
        }
    },
    removeHandler: function (element, type, handler) {
        if (element.removeEventListener) {
            element.removeEventListener(type, handler, false);
        } else if (element.detachEvent) {
            element.detachEvent("on" + type, handler);
        } else {
            element["on" + type] = null;
        }
    },
    getEvent: function (event) {
        return event ? event : window.event;
    },
    getTarget: function (event) {
        return event.target || event.srcElement;
    },

    preventDefault: function (event) {
        if (event.preventDefault) {
            event.preventDefault();
        } else {
            event.returnValue = false;
        }
    }
}; 

/自定义事件背后的概念是创建一个管理事件的对象,让其他对象监听那些事件。/

 function EventTarget(){

     this.handlers={};//事件的处理程序序列
 }

EventTarget.prototype = {
    constructor: EventTarget,
    addHandler: function (type, handler) {
        if (typeof this.handlers[type] == "undefined") {

            this.handlers[type] = [];
        }
        this.handlers[type].push(handler);
    },
         //激发事件
    fire: function (event) {
        if (!event.target) {
            event.target = this;
        }
        if (this.handlers[event.type] instanceof Array) {
            var handlers = this.handlers[event.type];
            for (var i = 0, len = handlers.length; i < len; i++) {
                handlers[i](event);
            }
        }
    },
    /*移除事件*/
    removeHandler: function (type, handler) {
        if (this.handlers[type] instanceof Array) {
            var handlers = this.handlers[type];
            for (var i = 0, len = handlers.length; i < len; i++) {
                if (handlers[i] === handler) {
                    break;
                }
            }
            handlers.splice(i, 1);

        }
    },

}; 

/拖拽实现/
//第一步:拖尾的基本代码需要为文档设置一个 onmousemove 事件处理程序,它总是将指定元素移动到鼠标指针的位置,
EventUtil.addHandler(document, “mousemove”, function (event) {
var myDiv = document.getElementById(“myDiv”);
myDiv.style.left = event.clientX + “px”;
myDiv.style.top = event.clientY + “px”;
});

        /*DragDrop 对象封装了拖放的所有基本功能。这是一个单例对象,并使用了模块模式来隐藏某些实
        现细节。*/
            var DragDrop = function () {
                var dragging = null;
                function handleEvent(event) {
                    //获取事件和目标
                    event = EventUtil.getEvent(event);
                    var target = EventUtil.getTarget(event);
                    //确定事件类型
                    switch (event.type) {
                        case "mousedown":
                            if (target.className.indexOf("draggable") > -1) {
                                dragging = target;
                            }
                            break;
                        case "mousemove":
                            if (dragging !== null) {
                                //指定位置
                                dragging.style.left = event.clientX + "px";
                                dragging.style.top = event.clientY + "px";
                            }
                            break;
                        case "mouseup":
                            dragging = null;
                            break;
                    }
                };
                //公共接口
                return {
                    enable: function () {
                        EventUtil.addHandler(document, "mousedown", handleEvent);
                        EventUtil.addHandler(document, "mousemove", handleEvent);
                        EventUtil.addHandler(document, "mouseup", handleEvent);
                    },

                    disable: function () {
                        EventUtil.removeHandler(document, "mousedown", handleEvent);
                        EventUtil.removeHandler(document, "mousemove", handleEvent);
                        EventUtil.removeHandler(document, "mouseup", handleEvent);
                    }
                }
            }(); 


            DragDrop.enable();

   </script>
</body>

</html>

6小结

JavaScript 中的函数非常强大,因为它们是第一类对象。使用闭包和函数环境切换,还可以有很多
使用函数的强大方法。可以创建作用域安全的构造函数,确保在缺少 new 操作符时调用构造函数不会改
变错误的环境对象。
 可以使用惰性载入函数,将任何代码分支推迟到第一次调用函数的时候。
 函数绑定可以让你创建始终在指定环境中运行的函数,同时函数柯里化可以让你创建已经填了
某些参数的函数。
 将绑定和柯里化组合起来,就能够给你一种在任意环境中以任意参数执行任意函数的方法。
ECMAScript 5 允许通过以下几种方式来创建防篡改对象。
 不可扩展的对象,不允许给对象添加新的属性或方法。
 密封的对象,也是不可扩展的对象,不允许删除已有的属性和方法。
 冻结的对象,也是密封的对象,不允许重写对象的成员。
JavaScript 中可以使用 setTimeout()和 setInterval()如下创建定时器。
 定时器代码是放在一个等待区域,直到时间间隔到了之后,此时将代码添加到 JavaScript 的处理
队列中,等待下一次 JavaScript 进程空闲时被执行。
 每次一段代码执行结束之后,都会有一小段空闲时间进行其他浏览器处理。
 这种行为意味着,可以使用定时器将长时间运行的脚本切分为一小块一小块可以在以后运行的
代码段。这种做法有助于 Web 应用对用户交互有更积极的响应。
JavaScript 中经常以事件的形式应用观察者模式。虽然事件常常和 DOM 一起使用,但是你也可以通
图灵社区会员 StinkBC(StinkBC@gmail.com) 专享 尊重版权

过实现自定义事件在自己的代码中应用。使用自定义事件有助于将不同部分的代码相互之间解耦,让维
护更加容易,并减少引入错误的机会。
拖放对于桌面和 Web 应用都是一个非常流行的用户界面范例,它能够让用户非常方便地以一种直
观的方式重新排列或者配置东西。在 JavaScrip 中可以使用鼠标事件和一些简单的计算来实现这种功能
类型。将拖放行为和自定义事件结合起来可以创建一个可重复使用的框架,它能应用于各种不同的情
况下。

鲍志强 wechat
欢迎你扫一扫上面的微信公众号,订阅我的博客!
你的点赞是为了你的未来